package com.mapperdb.builder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.annotation.ProviderSqlSource;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.Configuration;

import com.mapperdb.builder.annotation.ProviderSqlSourceSupper;
import com.mapperdb.builder.annotation.SelectKeyBuilder;
import com.mapperdb.core.Assert;
import com.mapperdb.db.ActiveRecordPlugin;
import com.mapperdb.db.ResultEntity;
import com.mapperdb.helper.Annt;
import com.mapperdb.joor.Reflect;
import com.mapperdb.mapper.base.MarkMapper;
import com.mapperdb.util.MapperUtils;

public class MapperBuilder {
	private Map<Class<?>, MapperBuilderAssistant> assistantMap = new HashMap<Class<?>, MapperBuilderAssistant>();
	private ActiveRecordPlugin activeRecordPlugin;
	private Set<Class<?>> processSet = new HashSet<Class<?>>();

	public MapperBuilder(ActiveRecordPlugin activeRecordPlugin) {
		this.activeRecordPlugin = activeRecordPlugin;
	}

	protected Configuration getConfiguration() {
		return activeRecordPlugin.getConfig().getConfiguration();
	}

	/**
	 * 配置完成后，执行下面的操作
	 * <br> 处理 configuration 中 全部的 MarkMapper Mapper 标记接口
	 * @param configuration
	 */
	public void processConfiguration(Class<?> mapperClass) {
		Assert.notNull(mapperClass, "Mapper class is null.");
		if (processSet.contains(mapperClass)) return;
		if (!MarkMapper.class.isAssignableFrom(mapperClass)) return;

		Iterator<?> iterator = getConfiguration().getMappedStatements().iterator();
		Map<MappedStatement, Class<?>> temp = new HashMap<MappedStatement, Class<?>>();
		boolean isSelectKey;
		boolean isProviderSqlSource;

		Object t = null;
		while (iterator.hasNext()) {
			t = iterator.next();
			if ((t instanceof MappedStatement)) {
				MappedStatement ms = (MappedStatement) t;

				// SelectKey MappedStatement
				isSelectKey = ms.getId().lastIndexOf(SelectKeyGenerator.SELECT_KEY_SUFFIX) != -1;
				// ProviderSqlSource MappedStatement
				isProviderSqlSource = ms.getSqlSource() instanceof ProviderSqlSource;
				if (!isSelectKey && isProviderSqlSource) {
					if (ms.getId().indexOf(mapperClass.getName()) != -1) {
						temp.put(ms, mapperClass);
					}
				}
			}
		}
		setSqlSources(temp);
		processSet.add(mapperClass);
	}

	protected void setSqlSources(Map<MappedStatement, Class<?>> map) {
		MappedStatement ms = null;
		Class<?> mapperClass = null;

		for (Entry<MappedStatement, Class<?>> e : map.entrySet()) {
			ms = e.getKey();
			mapperClass = e.getValue();
			Class<?> entityClass = MapperUtils.getGenericInterfaces0(mapperClass);
			Assert.notNull(entityClass, "Mapper " + mapperClass.getName() + " 没有指定泛型实例。");

			ResultEntity resultEntity = activeRecordPlugin.getConfig().getResultEntity(entityClass);
			// 尝试 解析 实体类 映射
			if (resultEntity == null) {
				resultEntity = activeRecordPlugin.addMapping(entityClass).getConfig().getResultEntity(entityClass);
			}
			setSqlSource(ms, mapperClass, resultEntity);
		}
	}

	protected void parseCache(Class<?> type, MapperBuilderAssistant assistant) {
		CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
		if (cacheDomain != null) {
			assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), cacheDomain.flushInterval(),
					cacheDomain.size(), cacheDomain.readWrite(), null);
		}
	}

	protected void parseCacheRef(Class<?> type, MapperBuilderAssistant assistant) {
		CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
		if (cacheDomainRef != null) {
			assistant.useCacheRef(cacheDomainRef.value().getName());
		}
	}

	protected MapperBuilderAssistant getMapperBuilderAssistant(Class<?> mapperClass) {
		MapperBuilderAssistant assistant = assistantMap.get(mapperClass);
		if (assistant == null) {
			synchronized (assistantMap) {
				if (assistant == null) {
					String resource = mapperClass.getName().replace('.', '/') + ".java (best guess)";
					assistant = new MapperBuilderAssistant(getConfiguration(), resource);
					assistant.setCurrentNamespace(mapperClass.getName());
					parseCache(mapperClass, assistant);
					parseCacheRef(mapperClass, assistant);
					assistantMap.put(mapperClass, assistant);
				}
			}
		}
		return assistant;
	}

	protected void setSqlSource(MappedStatement ms, Class<?> mapperClass, ResultEntity resultEntity) {
		String providerMethodName = MapperUtils.getName(ms.getId());
		Method m = Reflect.on(mapperClass).cacheMethodFrist(providerMethodName).<Method> get();

		Class<? extends Annotation> annotationClass = MapperUtils.getSqlProviderAnnotationType(m);
		if (annotationClass == null) return;

		Class<?> entityClass = resultEntity.entityClass();
		// 设置 返回值
		setResultType(ms, m, entityClass);

		Annotation annotation = m.getAnnotation(annotationClass);
		Annt annt = Annt.use(annotation);
		MapperBuilderAssistant assistant = getMapperBuilderAssistant(mapperClass);

		// builder SelectKey
		SelectKey selectKey = resultEntity.selectKey();
		if (InsertProvider.class == annotation.annotationType() && selectKey != null) {
			SelectKeyBuilder selectKeyBuilder = new SelectKeyBuilder(assistant, ms, resultEntity.selectKey());
			KeyGenerator keyGenerator = selectKeyBuilder.handleSelectKeyAnnotation();
			MapperUtils.setKeyGenerator(ms, keyGenerator, selectKey);
		}
		Class<?> providerType = annt.get("type");
		SqlSource sqlSource = new ProviderSqlSourceSupper(ms, resultEntity, providerType, providerMethodName);
		// 重写 SQL 源
		MapperUtils.setSqlSource(ms, sqlSource);
	}

	protected void setResultType(MappedStatement ms, Method m, Class<?> entityClass) {
		if (m == null) return;
		Class<?> type = m.getReturnType();
		if (!m.getReturnType().isPrimitive()) type = entityClass;
		MapperUtils.setResultType(ms, type);
	}
}
